import {
    FormProvided,
    FormItemProvided,
    InputPropsType,
    Rule_pattern,
    Rule_required,
    Rule_minLength,
    Rule_maxLength,
    Rule_min,
    Rule_max,
    Rule_decimal,
    Rule_earliest,
    Rule_latest,
    Rule_func,
    RuleAll,
} from '@/types'
import { inject, onMounted, onUnmounted, reactive, watch } from 'vue'
import utils from '@/common/utils'
import dayjs from 'dayjs'
import { $t } from '@/lang'

interface StateValidatorType {
    changeId: number
    valid?: boolean
    errorMessage?: string
}
interface StateType {
    validator: StateValidatorType
    keyForForm: symbol
}
export function useInput<TValue, TRule extends RuleAll>(
    props: InputPropsType<TValue, TRule>,
) {
    const form = inject<FormProvided | undefined>('form', undefined)
    const formItem = inject<FormItemProvided | undefined>('formItem', undefined)
    const state = reactive<StateType>({
        validator: {
            changeId: 0,
            valid: undefined,
            errorMessage: undefined,
        },
        keyForForm: Symbol(),
    })
    watch(
        () => props.value,
        () => {
            if (props.isInner) {
                return
            }
            state.validator.changeId++
        },
    )
    watch(
        () => props.rules,
        () => {
            if (props.isInner) {
                return
            }
            state.validator.changeId++
            methods.setInputRequired()
        },
    )
    watch(
        () => state.validator.changeId,
        () => {
            if (props.isInner) {
                return
            }
            state.validator.valid = true
            state.validator.errorMessage = undefined
            methods.validateWhenChanged(state.validator.changeId, props.value)
        },
    )
    watch(
        () => state.validator.valid,
        () => {
            if (props.isInner) {
                return
            }
            formItem?.value.setValidateResult(
                state.validator.valid,
                state.validator.errorMessage,
            )
        },
    )
    const validateFuncMap = new Map<
        RuleAll['name'],
        (
            rule: any,
            val: any,
        ) => [boolean, string?] | Promise<[boolean, string?]>
    >([
        [
            'required',
            (rule: Rule_required, val) => {
                if (!rule.value) {
                    return [true]
                }
                if (val === null || val === undefined || val === '') {
                    return [
                        false,
                        rule.errorMessage || $t('inputMixin.Required'),
                    ]
                }
                return [true]
            },
        ],
        [
            'minLength',
            (rule: Rule_minLength, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (val.length >= rule.value) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage ||
                        $t('inputMixin.MinLength', [rule.value]),
                ]
            },
        ],
        [
            'maxLength',
            (rule: Rule_maxLength, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (val.length <= rule.value) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage ||
                        $t('inputMixin.MaxLength', [rule.value]),
                ]
            },
        ],
        [
            'pattern',
            (rule: Rule_pattern, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (rule.value.test(val)) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage || $t('inputMixin.InValidFormat'),
                ]
            },
        ],
        [
            'min',
            (rule: Rule_min, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (val >= rule.value) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage || $t('inputMixin.Min', [rule.value]),
                ]
            },
        ],
        [
            'max',
            (rule: Rule_max, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (val <= rule.value) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage || $t('inputMixin.Max', [rule.value]),
                ]
            },
        ],
        [
            'decimal',
            (rule: Rule_decimal, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                if (utils.getDecimalCount(val) <= rule.value) {
                    return [true]
                }
                let defaultErrorMessage =
                    rule.value === 0
                        ? $t('inputMixin.MustBeInteger')
                        : $t('inputMixin.Decimal', [rule.value])
                return [false, rule.errorMessage || defaultErrorMessage]
            },
        ],
        [
            'earliest',
            (rule: Rule_earliest, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                let dt =
                    rule.value instanceof Date
                        ? rule.value
                        : new Date(rule.value)
                if (val >= dt) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage ||
                        $t('inputMixin.Earliest', [
                            dayjs(rule.value).format(rule.format),
                        ]),
                ]
            },
        ],
        [
            'latest',
            (rule: Rule_latest, val) => {
                if (val === null || val === undefined || val === '') {
                    return [true]
                }
                let dt =
                    rule.value instanceof Date
                        ? rule.value
                        : new Date(rule.value)
                if (val <= dt) {
                    return [true]
                }
                return [
                    false,
                    rule.errorMessage ||
                        $t('inputMixin.Latest', [
                            dayjs(rule.value).format(rule.format),
                        ]),
                ]
            },
        ],
        [
            'func',
            async (rule: Rule_func, val) => {
                let validResult = await rule.value(val)
                if (typeof validResult === 'boolean') {
                    if (validResult) {
                        return [true]
                    }
                    return [
                        false,
                        rule.errorMessage ?? $t('inputMixin.NonConformance'),
                    ]
                } else if (validResult instanceof Array) {
                    let [valid, errorMessage] = validResult
                    if (valid) {
                        return [true]
                    }
                    return [
                        false,
                        errorMessage ??
                            rule.errorMessage ??
                            $t('inputMixin.NonConformance'),
                    ]
                } else {
                    console.warn($t('inputMixin.FuncWarnning'))
                    return [true]
                }
            },
        ],
    ])
    onMounted(() => {
        if (props.isInner) {
            return
        }
        form?.value.addValidateFunc(state.keyForForm, methods.validate)
        methods.setInputRequired()
    })
    onUnmounted(() => {
        if (props.isInner) {
            return
        }
        form?.value.removeValidateFunc(state.keyForForm)
    })
    const methods = {
        async validateWhenChanged(changeId: number, val: TValue) {
            if (props.isInner) {
                return
            }
            for (let rule of props.rules!) {
                if (changeId !== state.validator.changeId) {
                    return
                }
                if (rule.delay ?? 0 > 0) {
                    await utils.sleep(rule.delay ?? 0)
                    if (changeId !== state.validator.changeId) {
                        return
                    }
                }
                let validateFunc = validateFuncMap.get(rule.name)
                if (typeof validateFunc !== 'function') {
                    continue
                }
                let result = await validateFunc(rule, val)
                if (result === null || result === undefined) {
                    continue
                }
                if (!result[0]) {
                    if (state.validator.valid !== false) {
                        state.validator.valid = false
                        state.validator.errorMessage = result[1]
                    }
                    break
                }
            }
        },
        async validate() {
            if (props.isInner) {
                return
            }
            for (let rule of props.rules!) {
                let validateFunc = validateFuncMap.get(rule.name)
                if (typeof validateFunc !== 'function') {
                    continue
                }
                let result = await validateFunc(rule, props.value)
                if (result === null || result === undefined) {
                    continue
                }
                if (!result[0]) {
                    if (state.validator.valid !== false) {
                        state.validator.valid = false
                        state.validator.errorMessage = result[1]
                    }
                    throw new Error(`${rule.name}:${result[1]}`)
                }
            }
        },
        setInputRequired() {
            if (props.isInner) {
                return
            }
            let required =
                (
                    props.rules!.find((a) => a.name === 'required') as
                        | Rule_required
                        | undefined
                )?.value ?? false
            formItem?.value.setInputRequired(required)
        },
    }
    return {
        ...state,
    }
}
